Decorator (Wrapper) Design Pattern
- It is a structural design pattern that lets you dynamically add new behaviroul and functionalities without modifying underlying code.
Problem
- Suppose we had a notification class that has a method
sendwhich initially send message as email, but we now want to give it to func of sms, slack and other messages as well. - One way to implement is to use inheritance and build class on top of current Notifier. But in that approach if we have to pick a combination like (slack+sms) then in that case i have to create classes for all possible combination.
Solution
- Most of the language in inheritance it is not possible to inherit the 2 classes so inheritance will not work.
- we need a way so we can plug and play with the new functionality based on requirements. like we can use Aggregation (object A contains objects B; B can live without A) or Composition (object A consists of objects B; A manages life cycle of B; B can’t live without A).
- Decorator is just like a wrapper which implement the same interface as wrapped object.
Structure
Below is the general structure where we have a base implmentation and multiple decorators to add functionality.
- Component: Common interface for wrapped and wrapee object.
- Concrete Component: A class that is already existing and need new functionalitis. It is the class or object that is being wrapped.
- Base Decorator: Base class of wrapper or decorator, It should have type same to Component so that it can accessed by other wrappers as well as existing client.
- Concrete Decorator (n): Implementation of base decorator, typically add new features dynamically.
- Client: kind of place where we combine all decorators in order to use
Implementation
- Here is the a decorator implementation for adding compress and encrypt functionalities to existing class of DataSource.
// Component interface
interface DataSource is
method writeData(data)
method readData():data
// Component Concrete class
class FileDataSource implements DataSource is
constructor FileDataSource(filename) { ... }
method writeData(data) is
// Write data to file.
method readData():data is
// Read data from file.
// Base decorator
class DataSourceDecorator implements DataSource is
protected field wrappee: DataSource
constructor DataSourceDecorator(source: DataSource) is
wrappee = source
// Simply delegate op to wrapee
method writeData(data) is
wrappee.writeData(data)
method readData():data is
return wrappee.readData()
// Concrete Decorator 1
class EncryptionDecorator extends DataSourceDecorator is
method writeData(data) is
// 1. Encrypt passed data.
// 2. Pass encrypted data to the wrappee's writeData
// method.
method readData():data is
// 1. Get data from the wrappee's readData method.
// 2. Try to decrypt it if it's encrypted.
// 3. Return the result.
// Concrete Decorator 2
class CompressionDecorator extends DataSourceDecorator is
method writeData(data) is
// 1. Compress passed data.
// 2. Pass compressed data to the wrappee's writeData
// method.
method readData():data is
// 1. Get data from the wrappee's readData method.
// 2. Try to decompress it if it's compressed.
// 3. Return the result.
// The app can assemble different stacks of decorators at
// runtime, depending on the configuration or environment.
class ApplicationConfigurator is
method configurationExample() is
source = new FileDataSource("salary.dat")
if (enabledEncryption)
source = new EncryptionDecorator(source)
if (enabledCompression)
source = new CompressionDecorator(source)
logger = new SalaryManager(source)
salary = logger.load()
// ...
Application
- When we want to add give extra behaviour to objects at runtime without breaking the code where objects is used.
- When it is not possible to extend the behaviour of object by using inheritance.
Pros and Cons
Pros
- Can extend obj behavious without making subclasses.
- add and remove responsibilities to objects at runtime
- Follow Single Resp. Principle.
Cons
- It is hard to implement a decorator where behaviour of decorator doesn't depend on the order of decorator stack.
- Hard to remove a specific wrapper from the wrapper stack.